package com.yycx.starter.shardingjdbc.config;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory;
import org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource;
import org.apache.shardingsphere.infra.config.algorithm.ShardingSphereAlgorithmConfiguration;
import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.strategy.sharding.StandardShardingStrategyConfiguration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 动态数据源配置：
 * 使用@DS注解，切换数据源@DS("sharding")
 */
@Configuration
@AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
public class DataSourceConfiguration {
    /**
     * 分表数据源名称
     */
    public static final String SHARDING_DATA_SOURCE_NAME = "sharding";
    /**
     * 动态数据源配置项
     */
    @Resource
    private DynamicDataSourceProperties properties;


    /**
     * shardingjdbc有四种数据源，需要根据业务注入不同的数据源
     *
     * <p>1. 未使用分片, 脱敏的名称(默认): shardingDataSource;
     * <p>2. 主从数据源: masterSlaveDataSource;
     * <p>3. 脱敏数据源：encryptDataSource;
     * <p>4. 影子数据源：shadowDataSource
     * <p>
     * shardingjdbc默认就是shardingDataSource
     * 如果需要设置其他的可以使用
     *
     * @Resource(value="") 设置
     */
//    @Lazy
//    @Resource
//    DataSource shardingSphereDataSource;

    /**
     * 将shardingDataSource放到了多数据源（dataSourceMap）中
     * 注意有个版本的bug，3.1.1版本 不会进入loadDataSources 方法，这样就一直造成数据源注册失败
     */
    @Bean
    public DynamicDataSourceProvider dynamicDataSourceProvider() {
        Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
        return new AbstractDataSourceProvider() {
            @Override
            public Map<String, DataSource> loadDataSources() {
                Map<String, DataSource> dataSourceMap = createDataSourceMap(datasourceMap);
//                List<Map<String, Object>> menus = new ArrayList<>();
//                try {
//                    ItemDataSource itemDataSource = (ItemDataSource) dataSourceMap.get("master");
//                    DataSource dataSource = itemDataSource.getDataSource();
//                    SqlRunner sqlRunner = new SqlRunner(dataSource.getConnection());
//                    String sql = "SELECT * FROM sharding_db";
//                    menus = sqlRunner.selectAll(sql);
//                    sqlRunner.closeConnection();
//                } catch (SQLException e) {
//                    e.printStackTrace();
//                }

                // 将 shardingjdbc 管理的数据源也交给动态数据源管理
                dataSourceMap.put(SHARDING_DATA_SOURCE_NAME, initConfig());
                return dataSourceMap;
            }
        };
    }

    /**
     * 将动态数据源设置为首选的
     * 当spring存在多个数据源时, 自动注入的是首选的对象
     * 设置为主要的数据源之后，就可以支持shardingjdbc原生的配置方式了
     *
     * @return
     */
    @Primary
    @Bean
    public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setPrimary(properties.getPrimary());
        dataSource.setStrict(properties.getStrict());
        dataSource.setStrategy(properties.getStrategy());
        dataSource.setProvider(dynamicDataSourceProvider);
        dataSource.setP6spy(properties.getP6spy());
        dataSource.setSeata(properties.getSeata());
        return dataSource;
    }

    private DataSource initConfig() {
        // 配置真实数据源
        Map<String, DataSource> dataSourceMap = new HashMap<>();
        // 配置第 1 个数据源
        //TODO 替换为数据库存储
        HikariDataSource dataSource1 = (HikariDataSource) createDataSource("archive_cloud", "101.132.152.211", "33061", "root", "yunyun@2022");
        dataSourceMap.put("archive_cloud", dataSource1);
        // 配置第 2 个数据源
        //HikariDataSource dataSource2 = (HikariDataSource) createDataSource("testdb1", "localhost", "3300", "root", "root");
        //dataSourceMap.put("testdb1", dataSource2);

        // 配置 arc_original 表规则
        ShardingTableRuleConfiguration orderTableRuleConfig =getOriginalTableRuleConfiguration();
        ShardingTableRuleConfiguration actInfoTableConfig =getActInfoTableRuleConfiguration();

        // 配置分库策略
        //orderTableRuleConfig.setDatabaseShardingStrategy(new StandardShardingStrategyConfiguration("userId", "database-inline"));

        // 配置分片规则
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTables().add(orderTableRuleConfig);
        shardingRuleConfig.getTables().add(actInfoTableConfig);
        //
        shardingRuleConfig.getBindingTableGroups().add("arc_original,arc_info");
        shardingRuleConfig.getKeyGenerators().put("snowflake", new ShardingSphereAlgorithmConfiguration("SNOWFLAKE", getProperties()));


        // 配置分库算法
        //Properties dbShardingAlgorithmrProps = new Properties();
        //dbShardingAlgorithmrProps.setProperty("algorithm-expression", "testdb${userid % 2}");
        // shardingRuleConfig.getShardingAlgorithms().put("database-inline", new ShardingSphereAlgorithmConfiguration("INLINE", dbShardingAlgorithmrProps));

        // 配置分表算法
        Properties tableShardingAlgorithmrProps = new Properties();
        tableShardingAlgorithmrProps.setProperty("algorithmClassName", "com.yycx.starter.shardingjdbc.algorithm.StandardModTableShardAlgorithm");
        tableShardingAlgorithmrProps.setProperty("strategy", "standard");
        shardingRuleConfig.getShardingAlgorithms().put("table-classbased", new ShardingSphereAlgorithmConfiguration("CLASS_BASED", tableShardingAlgorithmrProps));

        DataSource dataSource = null;
        try {
            dataSource = ShardingSphereDataSourceFactory.createDataSource(dataSourceMap, Collections.singleton(shardingRuleConfig), getShardingsphereProperties());
        } catch (SQLException e) {
            e.printStackTrace();
        }
        ShardingSphereDataSource shardingSphereDataSource = (ShardingSphereDataSource) dataSource;
        Map<String, ShardingSphereMetaData> test = shardingSphereDataSource.getContextManager().getMetaDataContexts().getMetaDataMap();

        return dataSource;
    }

    private static ShardingTableRuleConfiguration getOriginalTableRuleConfiguration() {
        //分片表配置
        ShardingTableRuleConfiguration orderTableRuleConfig = new ShardingTableRuleConfiguration("arc_original", "archive_cloud.arc_original$->{10000..11000}");
        // 配置分表策略
        orderTableRuleConfig.setTableShardingStrategy(new StandardShardingStrategyConfiguration("categoryId", "table-classbased"));
        return orderTableRuleConfig;
    }

    private static ShardingTableRuleConfiguration getActInfoTableRuleConfiguration() {
        //分片表配置
        ShardingTableRuleConfiguration orderTableRuleConfig = new ShardingTableRuleConfiguration("arc_info", "archive_cloud.arc_info$->{10000..11000}");
        // 配置分表策略
        orderTableRuleConfig.setTableShardingStrategy(new StandardShardingStrategyConfiguration("categoryId", "table-classbased"));
        return orderTableRuleConfig;
    }


    private static Properties getProperties() {
        Properties properties = new Properties();
        //工作机器唯一标识
        properties.setProperty("worker-id", "123");
        return properties;
    }

    // 配置
    private static Properties getShardingsphereProperties() {
        Properties propertie = new Properties();
        //是否打印SQL解析和改写日志
        propertie.setProperty("sql-show", "true");
        //用于SQL执行的工作线程数量，为零则表示无限制,默认值: CPU核数
        propertie.setProperty("executor.size", "4");
        //用于设置接收客户端请求的工作线程个数，默认为CPU核数*2
        propertie.setProperty("acceptor.size", "12");
        //每个物理数据库为每次查询分配的最大连接数量
        propertie.setProperty("max.connections.size.per.query", "1");
        //是否在启动时检查分表元数据一致性
        propertie.setProperty("check.table.metadata.enabled", "false");
        //对于单个大查询,每多少个网络包返回一次 128.
        propertie.setProperty("proxy.frontend.flush.threshold", "128");
        return propertie;
    }
    @Value("${spring.datasource.dynamic.datasource.master.url}")
    private String dburl;
    @Value("${spring.datasource.dynamic.datasource.master.username}")
    private String dbusername;
    @Value("${spring.datasource.dynamic.datasource.master.password}")
    private String dbpassword;
    @Value("${spring.datasource.dynamic.datasource.master.driver-class-name}")
    private String dbdrivername;


    private DataSource createDataSource(String dbName, String host, String port, String userName, String password) {
        HikariDataSource dataSource = new HikariDataSource();
//        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
//        dataSource.setJdbcUrl(String.format("jdbc:mysql://%s:%s/%s?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai", host, port, dbName));
//        dataSource.setUsername(userName);
//        dataSource.setPassword(password);
        dataSource.setDriverClassName(dbdrivername);
        dataSource.setJdbcUrl(dburl);
        dataSource.setUsername(dbusername);
        dataSource.setPassword(dbpassword);
        dataSource.setSchema("public");
        return dataSource;
    }

    private  void setRw(){
//配置读写分离规则         List<ReplicaQueryDataSourceRuleConfiguration> configurations = new ArrayList<>();         configurations.add(new ReplicaQueryDataSourceRuleConfiguration("ds", "master", slaveList, "load_balancer"));         Map<String, ShardingSphereAlgorithmConfiguration> loadBalancers = new HashMap<>();         //ROUND_ROBIN:轮询算法 RANDOM:随机访问算法         loadBalancers.put("load_balancer", new ShardingSphereAlgorithmConfiguration("ROUND_ROBIN", new Properties()));         ReplicaQueryRuleConfiguration ruleConfiguration = new ReplicaQueryRuleConfiguration(configurations, loadBalancers);         List<RuleConfiguration> ruleConfigurationList = new ArrayList<RuleConfiguration>();         ruleConfigurationList.add(ruleConfiguration);
    }

}
